﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian {

    /// <summary>
    /// 数据模型
    /// </summary>
    public interface IModel : IChangable {
    }

    public abstract class KHBD_NetModel : KHBD_Model {

        private record MessageListenerRecord(Type MessageType, Action<object> Listener);

        private record NetListenerCache(MethodInfo MethodInfo, Type MessageType);

        private static readonly IDictionary<Type, NetListenerCache[]> NetListenerCachesDictionary = new Dictionary<Type, NetListenerCache[]>();
        private HashSet<MessageListenerRecord> m_messageListenerRecordSet = new();

        public KHBD_NetModel() : base() {
            InitializeNetListener();
        }

        private void InitializeNetListener() {
            var type = GetType();
            if (!NetListenerCachesDictionary.TryGetValue(type, out var caches)) {
                var cacheSet = new HashSet<NetListenerCache>();
                var methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (var methodInfo in methodInfos) {
                    var netModelAttribute = methodInfo.GetCustomAttribute<NetListenerAttribute>();
                    if (netModelAttribute != null) {
                        var parameters = methodInfo.GetParameters();
                        Assert.IsTrue(parameters?.Length == 1);
                        var parameterType = parameters[0].ParameterType;
                        cacheSet.Add(new NetListenerCache(methodInfo, parameterType));
                    }
                }
                caches = NetListenerCachesDictionary[type] = cacheSet.ToArray();
            }
            foreach (var cache in caches) {
                var methodInfo = cache.MethodInfo;
                var args = new object[1];
                void listener(object message) {
                    args[0] = message;
                    methodInfo.Invoke(this, args);
                }
                AddNetModelListener(cache.MessageType, listener);
                m_messageListenerRecordSet.Add(new MessageListenerRecord(cache.MessageType, listener));
            }
        }

        protected override void Dispose() {
            foreach (var messageListenerRecord in m_messageListenerRecordSet) {
                RemoveNetModelListener(messageListenerRecord.MessageType, messageListenerRecord.Listener);
            }
            m_messageListenerRecordSet = null;
            base.Dispose();
        }

        protected abstract void AddNetModelListener(Type messageType, Action<object> listener);

        protected abstract void RemoveNetModelListener(Type messageType, Action<object> listener);
    }

    public abstract class KHBD_Model : KHBD_Object, IModel, IEntity {

        public event IChangable.ChangedDelegate Changed;

        private int m_EditingDepth;
        protected bool IsEditing => m_EditingDepth > 0;

        private bool m_HasEditDirty;

        public KHBD_Model() : base() {
            AutowireUtility.Autowire(this, (member, memberInfo) => {
                OnMemberInitialized(member, memberInfo);
                if (member is IChangable changable) {
                    changable.Changed += Dirty;
                }
            });
        }

        protected virtual void OnMemberInitialized(object member, MemberInfo memberInfo) {
        }

        protected void Dirty() {
            if (IsEditing) {
                m_HasEditDirty = true;
            } else {
                Changed?.Invoke();
            }
        }

        public void EditBegin() {
            m_EditingDepth++;
        }

        public void EditDone() {
            if (IsEditing) {
                if (--m_EditingDepth <= 0 && m_HasEditDirty) {
                    m_HasEditDirty = false;
                    Changed?.Invoke();
                }
            }
        }

        protected override void Dispose() {
            if (Changed != null) {
                Changed.Clear();
                Changed = null;
            }
            AutowireUtility.Dispose(this);
        }
    }
}